import {
    Uri,
	window,
    Position,
    QuickPick,
    workspace,
    QuickPickItem,
    Range,
} from "vscode";

import Gitee from "./gitee";
import Github from "./github";
import { Api, GistFiles, Gist } from "./api";

import * as fs from "fs";
import * as os from "os";
import * as path from "path";


interface Action {
    (item: QuickPickItemAction): void;
}

interface QuickPickItemAction extends QuickPickItem {
    action: Action;
}

export default class GistApi {
    private cache: string = `${ os.homedir() }/.vscodeGist`;
    private quickPick: QuickPick<QuickPickItemAction> = window.createQuickPick();
    private limit: number = 20;
    private page: number = 1;
    private publicPage: number = 1;
    private deletePage: number = 1;
    private deleteFilePage: number = 1;
    private addPage: number = 1;
    private gitee: Api = new Gitee();
    private github: Api = new Github();

	constructor() {
        try {
            fs.accessSync(this.cache, fs.constants.F_OK);
        } catch (error) {
            fs.mkdirSync(this.cache);
        }
        
		this.quickPick.canSelectMany = false;
		this.quickPick.matchOnDescription = true;
        this.quickPick.matchOnDetail = true;
        this.quickPick.busy = false;
        
		this.quickPick.onDidAccept(async () => {
            const item = this.quickPick.selectedItems[0];
            if (!this.quickPick.busy && item.action) {
                this.quickPick.busy = true;
                await item.action(item);
            }
        });
    }
    
    get api(): Api {
        const type: string = workspace.getConfiguration("gist").get("web") || "";
        switch (type) {
            case "gitee": return this.gitee;
            case "github": return this.github;
            default: return this.gitee;
        }
    }

    /**
     * 使用选中内容创建一个代码片段
     */
	public async createBySelect() {
		const edit = window.activeTextEditor;
		if (!edit) {
            window.showErrorMessage("当前未选中任何内容！");
			return;
        }
        const description = await window.showInputBox({ placeHolder: "代码片段描述" }) || "";
        const fileName = await window.showInputBox({ placeHolder: "文件名称" }) || "";
        const mode = await window.showInputBox({ placeHolder: "公开(true)/私有(false)，默认为公开" }) || "";
        await this.create(description, fileName, edit.document.getText(edit.selection), mode);
    }

    /**
     * 从文件创建一个代码片段
     * @param filePath 文件路径
     */
    public async createByFile(filePath: string) {
        try {
            const content = (await fs.promises.readFile(filePath)).toString();
            if (content === "") {
                window.showErrorMessage("文件内容不可为空！");
			    return;
            }
            const description = await window.showInputBox({ placeHolder: "代码片段描述" }) || "";
            const mode = await window.showInputBox({ placeHolder: "公开(true)/私有(false)，默认为公开" }) || "";
            await this.create(description, path.basename(filePath), content, mode);
        } catch (error) {
            window.showErrorMessage(`文件${ filePath }上传失败，错误信息：${ error }`);
        }
    }

    /**
     * 将选中内容添加到已有的代码片段
     */
    public async addBySelect() {
        const edit = window.activeTextEditor;
        if (!edit) {
            window.showErrorMessage("当前未选中任何内容！");
			return;
        }
        const fileName = await window.showInputBox({ placeHolder: "请输入文件名" }) || "";
        await this.add(fileName, edit.document.getText(edit.selection));
    }

    /**
     * 将文件添加到已有的代码片段
     * @param filePath 文件路径
     */
    public async addByFile(filePath: string) {
        try {
            const content = (await fs.promises.readFile(filePath)).toString();
            if (content === "") {
                window.showErrorMessage("文件内容不可为空！");
			    return;
            }
            await this.add(path.basename(filePath), content);
        } catch (error) {
            window.showErrorMessage(`文件${ filePath }上传失败，错误信息：${ error }`);
        }
    }

    /**
     * 删除一个代码片段
     */
    public async delete() {
        try {
            const gistList = await this.api.getList(this.limit, this.deletePage);
            const itemList: QuickPickItemAction[] = [];
            for (const gist of gistList) {
                gist.files = gist.files || [];
                itemList.push({
                    description: `共${ Object.keys(gist.files).length }个文件`,
                    label: gist.description,
                    action: async () => {
                        const result = await window.showWarningMessage(`确认删除代码片段【${ gist.description }】吗？`, "确认", "取消");
                        if (result !== "确认") {
                            return;
                        }
                        try {
                            await this.api.delete(gist.id || "");
                            window.showInformationMessage(`代码片段【${ gist.description }】已删除！`);
                        } catch (error) {
                            window.showErrorMessage(`删除${ gist.description }失败，错误信息：${ error }`);
                        }
                    },
                });
            }
            if (itemList.length === this.limit) {
                itemList.push({
                    description: `当前第${ this.deletePage }页`,
                    label: "下一页",
                    action: async () => {
                        this.deletePage ++;
                        await this.delete();
                    },
                });
            }
            if (this.deletePage !== 1) {
                itemList.unshift({
                    description: `当前第${ this.deletePage }页`,
                    label: "上一页",
                    action: async () => {
                        this.deletePage --;
                        await this.delete();
                    },
                });
            }
            this.selectItem(itemList, "删除选中的代码片段");
        } catch (error) {
            window.showErrorMessage(`获取文件列表出错，错误信息：${ error }`);
        }
    }

    /**
     * 删除代码片段中的一个文件
     */
    public async deleteFile() {
        try {
            const gistList = await this.api.getList(this.limit, this.deleteFilePage);
            const itemList: QuickPickItemAction[] = [];
            for (const gist of gistList) {
                gist.files = gist.files || [];
                itemList.push({
                    description: `共${ Object.keys(gist.files).length }个文件`,
                    label: gist.description,
                    action: async () => {
                        this.setFileList(gist.files, async (item: QuickPickItemAction) => {
                            const fileName = item.label;
                            const result = await window.showWarningMessage(`确认从【${ gist.description }】中删除文件【${ fileName }】吗？`, "确认", "取消");
                            if (result !== "确认") {
                                return;
                            }
                            try {
                                await this.api.deleteFile(gist.id || "", fileName);
                                window.showInformationMessage(`已成功从【${ gist.description }】中删除文件【${ fileName }】！`);
                            } catch (error) {
                                window.showErrorMessage(`从【${ gist.description }】中删除文件【${ fileName }】失败，错误信息为：${ error }`);
                            }
                        });
                    },
                });
            }
            if (itemList.length === this.limit) {
                itemList.push({
                    description: `当前第${ this.deleteFilePage }页`,
                    label: "下一页",
                    action: async () => {
                        this.deleteFilePage ++;
                        await this.deleteFile();
                    },
                });
            }
            if (this.deleteFilePage !== 1) {
                itemList.unshift({
                    description: `当前第${ this.deleteFilePage }页`,
                    label: "上一页",
                    action: async () => {
                        this.deleteFilePage --;
                        await this.deleteFile();
                    },
                });
            }
            this.selectItem(itemList, "代码片段列表");
        } catch (error) {
            window.showErrorMessage(`获取文件列表出错，错误信息：${ error }`);
        }
    }

    /**
     * 获取个人的代码片段
     */
	public async getList() {
        try {
            this.selectItem([], "加载中....");
            const gistList = await this.api.getList(this.limit, this.page);
            const itemList: QuickPickItemAction[] = [];
            for (const gist of gistList) {
                gist.files = gist.files || [];
                itemList.push({
                    description: `共${ Object.keys(gist.files).length }个文件`,
                    label: gist.description,
                    action: async () => {
                        this.setFileList(gist.files, async (item: QuickPickItemAction) => {
                            this.createTextEditor(gist, item.label, "user");
                        });
                    },
                });
            }
            if (itemList.length === this.limit) {
                itemList.push({
                    description: `当前第${ this.page }页`,
                    label: "下一页",
                    action: async () => {
                        this.page ++;
                        await this.getList();
                    },
                });
            }
            if (this.page !== 1) {
                itemList.unshift({
                    description: `当前第${ this.page }页`,
                    label: "上一页",
                    action: async () => {
                        this.page --;
                        await this.getList();
                    },
                });
            }
            this.selectItem(itemList, "代码片段列表");
        } catch (error) {
            window.showErrorMessage(`获取文件列表出错，错误信息：${ error }`);
        }
    }

    /**
     * 获取公开的代码片段
     */
    public async getPublicList() {
        try {
            this.selectItem([], "加载中....");
            const gistList = await this.api.getPublicList(this.limit, this.publicPage);
            const itemList: QuickPickItemAction[] = [];
            for (const gist of gistList) {
                gist.files = gist.files || [];
                itemList.push({
                    description: `作者: ${ gist.owner }，共${ Object.keys(gist.files).length }个文件`,
                    label: gist.description,
                    action: async () => {
                        this.setFileList(gist.files, async (item: QuickPickItemAction) => {
                            this.createTextEditor(gist, item.label, "public");
                        });
                    },
                });
            }
            if (itemList.length === this.limit) {
                itemList.push({
                    description: `当前第${ this.publicPage }页`,
                    label: "下一页",
                    action: async () => {
                        this.publicPage ++;
                        await this.getPublicList();
                    },
                });
            }
            if (this.publicPage !== 1) {
                itemList.unshift({
                    description: `当前第${ this.publicPage }页`,
                    label: "上一页",
                    action: async () => {
                        this.publicPage --;
                        await this.getPublicList();
                    },
                });
            }
            this.selectItem(itemList, "代码片段广场");
        } catch (error) {
            window.showErrorMessage(`获取文件列表出错，错误信息：${ error }`);
        }
    }

    /**
     * 编辑一个代码片段
     */
    public async edit() {
        const edit = window.activeTextEditor;
		if (!edit) {
            window.showErrorMessage("当前未打开任何代码片段！");
			return;
        }
        const docPath = path.resolve(edit.document.uri.fsPath);
        const docDir = path.dirname(path.dirname(docPath));
        const savePath = this.getSavePath("user");

        // 判断当前文档的路径是否为代码片段存储目录
        if (savePath.toLocaleLowerCase() !== docDir.toLocaleLowerCase()) {
            window.showErrorMessage(`当前文档不是可操作的代码片段，期望的文档路径为${ savePath }，当前却为${ docDir }`);
            return;
        }
        try {
            const fileName = path.basename(docPath);
            const gist = await this.api.getSingle(path.basename(path.dirname(docPath)));
            const description = await window.showInputBox({ placeHolder: `置空时表示不修改描述(${ gist.description })` }) || gist.description;
            gist.files[fileName].content = edit.document.getText();
            gist.description = description;
            await this.api.edit(gist);
            window.showInformationMessage(`代码片段【${ gist.description }】中的文件【${ fileName }】已同步跟新！`);
        } catch (error) {
            window.showErrorMessage(`代码片段修改失败，错误信息：${ error }`);
        }
    }

    /**
     * 新建代码片段
     * @param description 代码片段描述
     * @param fileName 文件名
     * @param content 文件内容
     * @param mode 是否公开
     */
    private async create(description: string, fileName: string, content: string, mode: string) {
        try {
            if (content === "") {
                window.showErrorMessage(`代码片段${ description }创建失败，内容不可为空！`);
                return;
            }
            await this.api.create({
                description,
                public: mode === "" ? true : mode === "false" ? false : true,
                files: {
                    [fileName]: { content },
                },
            });
            window.showInformationMessage(`代码片段【${ description }】已创建！`);
        } catch (error) {
          window.showErrorMessage(`代码片段${ description }创建失败，错误信息：${ error }`);
        }
    }

    /**
     * 将文件添加到已有的代码片段
     */
    private async add(fileName: string, content: string) {
        try {
            this.selectItem([], "加载中....");
            const gistList = await this.api.getList(this.limit, this.addPage);
            const itemList: QuickPickItemAction[] = [];
            for (const gist of gistList) {
                gist.files = gist.files || [];
                itemList.push({
                    description: `共${ Object.keys(gist.files).length }个文件`,
                    label: gist.description,
                    action: async (item: QuickPickItemAction) => {
                        try {
                            if (gist.files[fileName]) {
                                const result = await window.showWarningMessage(`代码片段【${ gist.description }】中【${ fileName }】已存在，是否覆盖？`, "是", "否");
                                if (result !== "是") {
                                    return;
                                }
                            }
                            gist.files = {
                                [fileName]: { content },
                            };
                            await this.api.edit(gist);
                            window.showInformationMessage(`文件【${ fileName }】已经成功添加到代码片段【${ gist.description }】中！`);
                        } catch (error) {
                            window.showErrorMessage(`文件${ fileName }添加到代码片段【${ gist.description }】中时失败，错误信息：${ error }`);
                        }
                        this.quickPick.hide();
                    },
                });
            }
            if (itemList.length === this.limit) {
                itemList.push({
                    description: `当前第${ this.addPage }页`,
                    label: "下一页",
                    action: async () => {
                        this.addPage ++;
                        await this.addBySelect();
                    },
                });
            }
            if (this.addPage !== 1) {
                itemList.unshift({
                    description: `当前第${ this.addPage }页`,
                    label: "上一页",
                    action: async () => {
                        this.addPage --;
                        await this.addBySelect();
                    },
                });
            }
            this.selectItem(itemList, "选择已有的代码片段");
        } catch (error) {
            window.showErrorMessage(`获取文件列表出错，错误信息：${ error }`);
        }
    }

    /**
     * 生成文件列表
     * @param files 代码片段列表
     * @param action 点击列表项时的回调
     */
    private setFileList(files: GistFiles, action: Action) {
        const itemList: QuickPickItemAction[] = [];
        for (const fileName in files) {
            itemList.push({
                action,
                label: fileName,
            });
        }
        this.selectItem(itemList, "文件列表");
    }

    /**
     * 
     * @param items 列表项
     * @param placeholder 输入框提示
     */
    private selectItem(items: QuickPickItemAction[], placeholder: string) {
        this.quickPick.busy = false;
		this.quickPick.value = '';
		this.quickPick.items = items;
        this.quickPick.placeholder = placeholder;
		this.quickPick.show();
    }

    /**
     * 获取代码片段临时存储目录
     */
    private getSavePath(type: string) {
        const web: string = workspace.getConfiguration("gist").get("type") || "";
        const savePath: string = workspace.getConfiguration("gist").get("savePath") || this.cache;
        return path.resolve(path.join(savePath, web, type));
    }

    /**
     * 打开新的文件编辑窗口
     * @param gist 代码片段
     * @param fileName 文件名
     * @param type 代码片段类型: true(个人)/false(代码片段广场)
     */
    private async createTextEditor(gist: Gist, fileName: string, type: string) {
        const filePath = `${ this.getSavePath(type) }/${ gist.id }/${ fileName }`;
        let agreement: string;
        try {
            // 检测文件是否已存在
            fs.accessSync(filePath);
            agreement = "file:///";
        } catch (error) {
            agreement = "untitled:";
        }
        const uri = Uri.parse(`${ agreement }${ this.getSavePath(type) }/${ gist.id }/${ fileName }`);
        const doc = await workspace.openTextDocument(uri);
        const textEditor = await window.showTextDocument(doc, { preview: true });
        const content = await this.api.getContent(gist.files[fileName]);
        textEditor.edit((editBuilder) => {
            if (agreement === "file:///") {
                // 本地文件已存在的先清空，显示最新的
                editBuilder.delete(new Range(new Position(0, 0), new Position(doc.lineCount, 0)));
            }
            editBuilder.insert(new Position(0, 0) , content);
        });
    }
}